#include <Windows.h>
#include <stdlib.h>
#include <string.h>
#include <tchar.h>
#include <stdio.h>
#include <malloc.h>
#include <strsafe.h>
#include <math.h>
#include "resource.h"

#define BUFFERSIZE 4096

static TCHAR szWindowClass[]=_T("Codarea Huffman");
static TCHAR szTitle[]=_T("Codarea Huffman");

HINSTANCE hInst;

HWND hAboutWnd = NULL;
HWND hHowWnd = NULL;
HWND hWhatWnd = NULL;
HWND hEditWnd = NULL;

typedef struct nodHuffman
{
	int frecv, stanga, dreapta, parinte;
	bool liber;
}nodHuffman;

nodHuffman huff[255];
char coduri[BUFFERSIZE][8], textCodat[4 * BUFFERSIZE], *buffer;
int dimBuf;

void initializareHuffman()
{
	for (int i = 0; i < 255; i++)
	{
		huff[i].stanga = -1;
		huff[i].dreapta = -1;
		huff[i].liber = true;
		huff[i].parinte = -1;
	}
}

void calculeazaFrecvente(char* textBuffer)
{
	int lung = strlen(textBuffer);
	for (int i = 0; i < lung; i++)
		huff[textBuffer[i]].frecv++;
}

int indexMin(int n, int j)
{
	int min = INT_MAX;
	int index = 0;
	for (int i = 0; i < n; i++)
	{
		if (huff[i].liber)
		{
			if (huff[i].frecv < min && i != j)
			{
				min = huff[i].frecv;
				index = i;
			}
		}
	}
	return index;
}

void construiesteArboreHuffman()
{
	int n = 128;
	while (n < 255)
	{
		int i = indexMin(n, -1);
		int j = indexMin(n, i);
		huff[n].frecv = huff[i].frecv + huff[j].frecv;
		huff[n].liber = true;
		huff[n].stanga = i;
		huff[n].dreapta = j;
		huff[i].liber = false;
		huff[j].liber = false;
		huff[i].parinte = n;
		huff[j].parinte = n;
		n++;
	}
}

void construiesteVocabular()
{
	int nod, parinte; char cod[128];
	for (int i = 0; i < 128; i++)
	{
		nod = i;
		strcpy(coduri[i], "");
		while (nod != 254)
		{
			parinte = huff[nod].parinte;
			if (huff[parinte].stanga == nod)
			{
				strcpy(cod, coduri[i]);
				strcpy(coduri[i], "0");
				strcat(coduri[i], cod);
			}
			else
			{
				strcpy(cod, coduri[i]);
				strcpy(coduri[i], "1");
				strcat(coduri[i], cod);
			}
			nod = parinte;
		}
	}
}

void compresieHuffman(char *textBuffer)
{
	int lung = strlen(textBuffer);
	dimBuf = 0;
	char cod[128], byte = 0, biti = 0;
	*textCodat = NULL;
	for (int i = 0; i < lung; i++)
	{
		char c = textBuffer[i];
		strcpy(cod, coduri[c]);
		strcat(textCodat, cod);
		for (int j = 0; j < strlen(cod); j++)
		{
			byte = 2*byte + cod[j] - '0';
			biti++;
			if (biti == 8)
			{
				buffer[dimBuf] = byte;
				dimBuf++;
				biti = 0;
				byte = 0;
			}
		}
	}
	buffer[dimBuf] = byte << (8 - biti);
	dimBuf++;
	buffer[dimBuf] = biti;
	dimBuf++;
}

char *decompresieHuffman()
{
	char *textBuffer = (char *) malloc (256 * sizeof (char));
	strcpy(textBuffer, "");
	char cod[128];
	strcpy(cod, "");
	char cuvant[2];
	cuvant[1] = 0;
	for (int i = 0; i <= dimBuf-2; i++)
	{
		char byte = buffer[i];
		int lastBit = 0;
		if (i == dimBuf - 2) lastBit = 8 - buffer[dimBuf - 1];
		for (int j = 7; j >= lastBit; j--)
		{
			int bit = (byte / (1 << j)) % 2;
			cuvant[0] = '0' + bit;
			strcat(cod, cuvant);
			for (int k = 0; k < 128; k++)
			{
				if (strcmp (coduri[k], cod) == 0)
				{
					cuvant[0] = k;
					strcat(textBuffer, cuvant);
					strcpy(cod, "");
					break;
				}
			}
		}
	}
	return textBuffer;
}

//Functia pentru crearea dialogului "Ce este Codarea Huffman?"

BOOL CALLBACK WhatDialog(HWND hWhatWnd,
						 UINT Message,
						 WPARAM wParam,
						 LPARAM lParam)
{

	//DialogBox-ul trebuie sa se inchida la apasarea singurului buton (OK) existent
	//sau la apasarea pe X-ul din colt

	switch(Message)
	{
	case WM_COMMAND:
		switch (LOWORD(wParam))
		{
		case IDOK:
			ShowWindow(hWhatWnd, SW_HIDE);
			break;
		}
		break;
	case WM_CLOSE:
		ShowWindow(hWhatWnd, SW_HIDE);
		break;
	default:
		return FALSE;
	}
	return TRUE;
}

//Functia pentru crearea dialogului "Cum se foloseste programul?"

BOOL CALLBACK HowDialog(HWND hHowWnd,
						UINT Message,
						WPARAM wParam,
						LPARAM lParam)
{

	//DialogBox-ul trebuie sa se inchida la apasarea singurului buton (OK) existent
	//sau la apasarea pe X-ul din colt

	switch(Message)
	{
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case IDOK:
			ShowWindow(hHowWnd, SW_HIDE);
			break;
		}
		break;
	case WM_CLOSE:
		ShowWindow(hHowWnd, SW_HIDE);
		break;
	default:
		return FALSE;
	}
	return TRUE;
}

//Functia pentru crearea dialogului "Despre noi"

BOOL CALLBACK AboutDialog(HWND hAboutWnd,
						  UINT Message,
						  WPARAM wParam,
						  LPARAM lParam)
{

	//DialogBox-ul trebuie sa se inchida la apasarea singurului buton (OK) existent
	//sau la apasarea pe X-ul din colt

	switch(Message)
	{
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case IDOK:
			ShowWindow(hAboutWnd, SW_HIDE);
			break;
		}
		break;
	case WM_CLOSE:
		ShowWindow(hAboutWnd, SW_HIDE);
		break;
	default:
		return FALSE;
	}
	return TRUE;
}

//Functia pentru crearea casetei de introducere/editare a textului

BOOL CALLBACK CreateEditBox(HWND hEditWnd,
							UINT Message,
							WPARAM wParam,
							LPARAM lParam)
{
	char textBuffer[BUFFERSIZE];

	switch(Message)
	{
	case WM_COMMAND:
		switch(LOWORD(wParam))
		{
		case IDOK:
			{
				GetDlgItemTextA(hEditWnd,
					IDC_EDITCODARE,
					textBuffer,
					BUFFERSIZE);
				
				initializareHuffman();
				calculeazaFrecvente(textBuffer);
				construiesteArboreHuffman();
				construiesteVocabular();

				HANDLE hOutput;

				hOutput = CreateFile(_T("outputCodeHuffman.bin"),
					GENERIC_WRITE,
					0,
					NULL,
					CREATE_ALWAYS,
					FILE_ATTRIBUTE_NORMAL,
					NULL);

				buffer = (char *) malloc (sizeof (char) * strlen (textBuffer) + 2);
				compresieHuffman(textBuffer);

				DWORD dwBytesToWrite = (DWORD) strlen (textCodat);
				DWORD dwBytesWritten = 0;

				WriteFile(hOutput,
					textCodat,
					dwBytesToWrite,
					&dwBytesWritten,
					NULL);

				MessageBox(NULL,
					_T("Text codat!"),
					_T("Informatii"),
					MB_OK);
								
				CloseHandle(hOutput);
			}
			break;
		case IDCANCEL:
			ShowWindow(hEditWnd, SW_HIDE);
			break;
		}
		break;
	case WM_CLOSE:
		ShowWindow(hEditWnd, SW_HIDE);
		break;
	default:
		return FALSE;
	}
	return TRUE;

}

//Functia de incarcare a unui fisier text

BOOL IncarcaFisier(HWND newWnd,
				   LPCTSTR pszFileName,
				   LPCTSTR pszFileName2)
{

	//hFisier este un handle catre fisierul ce urmeaza a fi incarcat

	HANDLE hFisier, hOutput;

	//Functiei CreateFile transmitem ca dorim deschiderea unui fisier EXISTENT pentru CITIRE

	hFisier = CreateFile(pszFileName,
		GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		0, NULL);

	//Daca este incarcat cu succes fisierul

	if (hFisier != INVALID_HANDLE_VALUE)
	{

		//In dwDimFisier se retine dimensiunea acestuia

		DWORD dwDimFisier;
		dwDimFisier = GetFileSize(hFisier, NULL);
		if (dwDimFisier != 0xFFFFFFFF)
		{

			//In pszTextFisier se va retine textul din fisier

			LPSTR pszTextFisier;

			//Alocam spatiu cat pentru dimensiunea fisierului + 1 (pentru caracterul de final de fisier)

			pszTextFisier = (LPSTR) GlobalAlloc(GPTR, dwDimFisier + 1);

			//Daca nu exista text retinut in pszTextFisier

			if (pszTextFisier != NULL)
			{

				//Retinem din hFisier in pszTextFisier un numar de dwDimFisier de bytes

				DWORD dwCiteste;

				if (ReadFile(hFisier,
					pszTextFisier,
					dwDimFisier,
					&dwCiteste, NULL))
				{
					pszTextFisier[dwDimFisier] = '\0';	//Initializam ultima pozitie din bufferul cu textul din fisier
					initializareHuffman();
					calculeazaFrecvente(pszTextFisier);
					construiesteArboreHuffman();
					construiesteVocabular();
					buffer = (char *) malloc (sizeof(char) * strlen(pszTextFisier) + 2);
					compresieHuffman(pszTextFisier);
				}

				//Eliberam memoria

				hOutput = CreateFile(pszFileName2,
					GENERIC_WRITE,
					0,
					NULL,
					CREATE_ALWAYS,
					0, NULL);

				DWORD dwBytesToWrite = (DWORD) strlen (textCodat);
				DWORD dwBytesWritten = 0;

				WriteFile(hOutput,
					textCodat,
					dwBytesToWrite,
					&dwBytesWritten,
					NULL);

				MessageBox(NULL,
					_T("Text codat!"),
					_T("Informatii"),
					MB_OK);
				GlobalFree(pszTextFisier);
			}
		}

		//Inchidem fisierul

		CloseHandle(hOutput);
		CloseHandle(hFisier);
	}
	return TRUE;
}

//Functia de incarcare a unui fisier binar

BOOL IncarcaFisierBinar(HWND newWnd,
						LPCTSTR pszFileName,
						LPCTSTR pszFileName2)
{

	//hFisier este un handle catre fisierul ce urmeaza a fi incarcat

	HANDLE hFisier, hOutput;

	//Functiei CreateFile transmitem ca dorim deschiderea unui fisier EXISTENT pentru CITIRE

	hFisier = CreateFile(pszFileName,
		GENERIC_READ,
		FILE_SHARE_READ,
		NULL,
		OPEN_EXISTING,
		0, NULL);

	//Daca este incarcat cu succes fisierul

	if (hFisier != INVALID_HANDLE_VALUE)
	{

		//In dwDimFisier se retine dimensiunea acestuia

		DWORD dwDimFisier;
		dwDimFisier = GetFileSize(hFisier, NULL);
		if (dwDimFisier != 0xFFFFFFFF)
		{

			//In pszTextFisier se va retine textul din fisier

			LPSTR pszTextFisier;

			//Alocam spatiu cat pentru dimensiunea fisierului + 1 (pentru caracterul de final de fisier)

			pszTextFisier = (LPSTR) GlobalAlloc(GPTR, dwDimFisier + 1);

			//Daca nu exista text retinut in pszTextFisier

			if (pszTextFisier != NULL)
			{

				//Retinem din hFisier in pszTextFisier un numar de dwDimFisier de bytes

				DWORD dwCiteste;

				if (ReadFile(hFisier,
					pszTextFisier,
					dwDimFisier,
					&dwCiteste,
					NULL))
				pszTextFisier[dwDimFisier] = '\0';	//Initializam ultima pozitie din bufferul cu textul din fisier

				hOutput = CreateFile(pszFileName2,
					GENERIC_WRITE,
					0,
					NULL,
					CREATE_ALWAYS,
					0, NULL);

				DWORD dwBytesToWrite = (DWORD) strlen (textCodat);
				DWORD dwBytesWritten = 0;

				WriteFile(hOutput,
					decompresieHuffman(),
					dwBytesToWrite,
					&dwBytesWritten,
					NULL);

				//Eliberam memoria

				GlobalFree(pszTextFisier);

				MessageBox(NULL,
					_T("Text decodat!"),
					_T("Informatii"),
					MB_OK);
			}
		}

		//Inchidem fisierul

		CloseHandle(hOutput);
		CloseHandle(hFisier);
	}
	return TRUE;
}

LRESULT CALLBACK WndProc(HWND newWnd,
						 UINT mMessage,
						 WPARAM wParam,
						 LPARAM lParam)
{
	switch (mMessage)
	{
	case WM_COMMAND:
		switch (LOWORD(wParam))
		{

			//Incarcare fisier binar

		case ID_INCARCACOD:
			{
				OPENFILENAME ofn;
				char szFileName[MAX_PATH] = "";

				ZeroMemory(&ofn, sizeof(ofn));	//Initializam memoria cu 0

				ofn.lStructSize = sizeof(ofn);
				ofn.hwndOwner = newWnd;		//Fereastra "parinte" in care se incarca fisierul binar
				ofn.lpstrFilter = _T("Binary Files (*.bin)\0*.bin\0All Files (*.*)\0*.*\0");	//Caseta cu alegerea tipului de fisier
				ofn.lpstrFile = (LPWSTR) szFileName;
				ofn.nMaxFile = MAX_PATH;
				ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST;
				ofn.lpstrDefExt = _T("bin");	//Daca utilizatorul introduce in searchbar doar "fisier",
												//inainte sa renunte si sa dea eroare, programul va cauta fisierul "fisier.bin"

				if (GetOpenFileName(&ofn))	//Daca se deschide cu succes fisierul se executa functia de incarcare
				{
					IncarcaFisierBinar(newWnd,
						(LPCTSTR) szFileName,
						_T("outputDecodeHuffman.txt"));
				}
			}
			break;

			//Introducere text de la tastatura

		case ID_INTRODUTEXT:
			{
				//Se creaza un dialog box ce contine caseta de introducere/editare text

				bool CLICK = DialogBox(GetModuleHandle(NULL),
					MAKEINTRESOURCE(IDD_CODARE),
					hEditWnd,
					CreateEditBox);
				if (CLICK) ShowWindow(hEditWnd, SW_SHOW);
			}
			break;

			//Incarcare fisier text

		case ID_INCARCATEXT:
			{
				OPENFILENAME ofn;
				char szFileName[MAX_PATH] = "";

				ZeroMemory(&ofn, sizeof(ofn));	//Initializam memoria cu 0

				ofn.lStructSize = sizeof(ofn);
				ofn.hwndOwner = newWnd;		//Fereastra "parinte" in care se incarca fisierul text
				ofn.lpstrFilter = _T("Text Files (*.txt)\0*.txt\0All Files (*.*)\0*.*\0");	//Caseta cu alegerea tipului de fisier
				ofn.lpstrFile = (LPWSTR) szFileName;
				ofn.nMaxFile = MAX_PATH;
				ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST;
				ofn.lpstrDefExt = _T("txt");	//Daca utilizatorul introduce in searchbar doar "fisier",
												//inainte sa renunte si sa dea eroare, programul va cauta fisierul "fisier.txt"

				if (GetOpenFileName(&ofn))	//Daca se deschide cu succes fisierul se executa functia de incarcare
				{
					IncarcaFisier(newWnd,
						(LPCTSTR) szFileName,
						_T("outputCodeHuffman2.bin"));
				}
			}
			break;
		case ID_IESIRE:
			PostMessage(newWnd, WM_CLOSE, 0, 0);
			break;
		case ID_CEESTE:
			{

				//Crearea dialogului "Ce este...?"

				bool CLICK = DialogBox(GetModuleHandle(NULL),
					MAKEINTRESOURCE(IDD_WHAT),
					hWhatWnd,
					WhatDialog);
				if (CLICK) ShowWindow(hWhatWnd, SW_SHOW);
			}
			break;
		case ID_CUMSEFOL:
			{

				//Crearea dialogului "Cum se foloseste... ?"

				bool CLICK = DialogBox(GetModuleHandle(NULL),
					MAKEINTRESOURCE(IDD_HOW),
					hHowWnd,
					HowDialog);
				if (CLICK) ShowWindow(hHowWnd, SW_SHOW);
			}
			break;
		case ID_DESPRE:
			{

				//Crearea dialogului "Despre noi"

				bool CLICK = DialogBox(GetModuleHandle(NULL),
					MAKEINTRESOURCE(IDD_ABOUT),
					hAboutWnd,
					AboutDialog);
				if (CLICK) ShowWindow(hAboutWnd, SW_SHOW);
			}
			break;
		}
	case WM_CLOSE:
		DestroyWindow(newWnd);
		break;
	case WM_DESTROY:
		PostQuitMessage(0);
		break;
	default:
		return DefWindowProc(newWnd, mMessage, wParam, lParam);
	}
	return 0;
}

int WINAPI WinMain(HINSTANCE hInstance,
				   HINSTANCE hPrevInstance,
				   LPSTR lpCmdLine,
				   int nCmdShow)
{
	WNDCLASSEX mainWnd;
	mainWnd.cbSize = sizeof(WNDCLASSEX);
	mainWnd.style = CS_HREDRAW | CS_VREDRAW;
	mainWnd.lpfnWndProc = WndProc;
	mainWnd.cbClsExtra = 0;
	mainWnd.cbWndExtra = 0;
	mainWnd.hInstance = hInstance;
	mainWnd.hIcon = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_PNG1));
	mainWnd.hCursor = LoadCursor(NULL, IDC_ARROW);
	mainWnd.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
	mainWnd.lpszMenuName = MAKEINTRESOURCE(IDR_MENU1);
	mainWnd.lpszClassName = szWindowClass;
	mainWnd.hIconSm = LoadIcon(GetModuleHandle(NULL), MAKEINTRESOURCE(IDB_PNG1));
	
	if (!RegisterClassEx(&mainWnd))
	{
		MessageBox(NULL,
			_T("Apelarea functiei RegisterClassEx a esuat!"),
			_T("Codarea Huffman"),
			NULL);
		return 1;
	}
	
	HWND newWnd = CreateWindow(szWindowClass,
		szTitle,
		WS_OVERLAPPEDWINDOW,
		535, 220,
		500, 400,
		NULL,
		NULL,
		hInstance,
		NULL);

	if (!newWnd)
	{
		MessageBox(NULL,
			_T("Apelarea functiei CreateWindow a esuat!"),
			_T("Codarea Huffman"),
			NULL);
		return 1;
	}

	ShowWindow(newWnd, nCmdShow);
	UpdateWindow(newWnd);

	MSG mMessage;
	while (GetMessage(&mMessage, NULL, 0, 0))
	{
		TranslateMessage(&mMessage);
		DispatchMessage(&mMessage);
	}

	return (int) mMessage.wParam;
}